package com.run_java_test.jar_upload_run.controller.v2;

import com.run_java_test.jar_local_run.service.JarUtils;
import com.run_java_test.jar_upload_run.services.v2.JarUploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@RequestMapping("test_jars_v2")
@RestController
public class JarFileControllerV2 {

    // class 的 set 集合
    private final Set<Class<?>> classesToRegister = new HashSet<>();

    // 记录的 bean 集合
    private final Set<String> registeredBeanNames = new HashSet<>();

    // 引入需要使用的bean组件
    // ----------------------------------------------------------------------------------------------------------------
    private final ApplicationContext applicationContext;
    private final JarUploadService jarUploadService;
    private final DefaultListableBeanFactory defaultListableBeanFactory;

    @Autowired
    public JarFileControllerV2(
            ApplicationContext applicationContext,
            JarUploadService jarUploadService,
            DefaultListableBeanFactory defaultListableBeanFactory
    ) {
        this.applicationContext = applicationContext;
        this.jarUploadService = jarUploadService;
        this.defaultListableBeanFactory = defaultListableBeanFactory;
    }
    // ----------------------------------------------------------------------------------------------------------------


    /**
     * 这个方法上传的jar文件中, 暂时无法处理 configuration 这种 spring 配置类, mapper 这种 mybatis 的方法
     *
     * @param file 需要上传的 jar 文件
     * @return String 返回字符串
     * @throws Exception 可能会出现的异常
     */
    @PostMapping("upload_jar")
    public String uploadJar(@RequestParam("file") MultipartFile file) {
        // 当前项目同级目录下
        String uploadDirPath = new File("uploads").getAbsolutePath();
        // 构造文件对象
        File uploadDir = new File(uploadDirPath);
        // 如果目录不存在，则创建
        if (!uploadDir.exists()) {
            // 创建多层目录
            uploadDir.mkdirs();
        }
        // 定义存储的目标文件
        File jarFile = new File(uploadDir, file.getOriginalFilename());
        // 输出文件路径
        System.out.println("文件上传到的位置 --- File uploaded to: " + jarFile.getAbsolutePath());
        // 将上传的文件写入目标文件
        try {
            file.transferTo(jarFile);
        } catch (IOException e) {
            System.out.println("将上传的文件写入目标文件出现错误");
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        // ----------------------------------------------------------------------------------------------------------------

        // 获取jar的文件类路径
        URL jarUrl = null;
        try {
            jarUrl = jarFile.toURI().toURL();
        } catch (MalformedURLException e) {
            System.out.println("获取jar的文件类路径出现错误");
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        // new URLClassLoader(new URL[]{jarUrl}, Thread.currentThread().getContextClassLoader()) 一般web或者插件开发,热加载等 使用这个类加载器
        // new URLClassLoader(new URL[]{jarUrl}, this.getClass().getClassLoader()) 一般是不用热加载的
        // 遍历所有class文件保存
        URLClassLoader classLoader = null;
        try {
            classLoader = new URLClassLoader(new URL[]{jarUrl}, Thread.currentThread().getContextClassLoader());
            // 遍历 JAR 文件的所有类
            for (String className : JarUtils.getClassNamesFromJar(jarFile)) {
                Class<?> clazz = Class.forName(className, true, classLoader);
                // 保存 class
                classesToRegister.add(clazz);
            }
        } catch (Exception e) {
            System.out.println("解析jar包中的文件出现错误");
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            // 关闭 jar 加载器
            try {
                classLoader.close();
            } catch (IOException e) {
                System.out.println("关闭 jar 加载器出现错误");
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        System.out.println("遍历 JAR 文件的所有类 完成");
        // ----------------------------------------------------------------------------------------------------------------

        // 第一步：注册所有 BeanDefinition 相当于定义bean (注册带有spring注解的bean)
        for (Class<?> clazz : classesToRegister) {
            if (
                    clazz.isAnnotationPresent(Component.class) ||
                            clazz.isAnnotationPresent(Service.class) ||
                            clazz.isAnnotationPresent(RestController.class) ||
                            clazz.isAnnotationPresent(Controller.class)
            ) {
                // 注册bean, 但是没有被实例化
                GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
                // 设置class
                beanDefinition.setBeanClass(clazz);
                // 单例模式
                beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
                // bean名称
                String beanName = Character.toLowerCase(clazz.getSimpleName().charAt(0)) + clazz.getSimpleName().substring(1);
                // 注册bean, 设置名称 设置bean定义
                defaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinition);

                // 记录已注册的 Bean 名称
                registeredBeanNames.add(beanName);
            }
        }
        System.out.println("注册所有 BeanDefinition 完成");
        // ----------------------------------------------------------------------------------------------------------------

        // 第二步：实例化所有 Bean 这个是bean进行实例化 bean由于懒加载的原因, 需要获取一下bean, bean就可以被实例化
        for (String beanName : registeredBeanNames) {
            if (defaultListableBeanFactory.containsBeanDefinition(beanName)) {
                defaultListableBeanFactory.getBean(beanName); // 确保依赖解析完成
                System.out.println("Bean 加载: " + beanName);
            }
        }
        System.out.println("实例化所有 BeanDefinition 完成");
        // ----------------------------------------------------------------------------------------------------------------

        // 第三步: 处理 controller
        List<Class<?>> classList = classesToRegister.stream().toList();
        for (int i = 0; i < classList.size(); i++) {
            // 将每个class文件进行注册到spring并且将端点映射到方法上
            jarUploadService.registerController(classList.get(i));
        }
        System.out.println("处理 controller 完成");
        // ----------------------------------------------------------------------------------------------------------------

        System.out.println("上传jar包完成");
        // 最后方法执行完成
        return "JAR uploaded and classes registered.";
    }
    // ----------------------------------------------------------------------------------------------------------------

    /**
     * 卸载导入的 jar 中的类
     *
     * @return String
     */
    @PostMapping("unload_jar")
    public String unloadJar() {
        // 导入jar的时候, 保存解析出来的class文件
        List<Class<?>> loadedClasses = classesToRegister.stream().toList();
        // 首先卸载controller映射
        for (int i = 0; i < loadedClasses.size(); i++) {
            // 删除 地址对应方法中的 controller 映射
            jarUploadService.unregisterController(loadedClasses.get(i));
        }
        // ----------------------------------------------------------------------------------------------------------------

        // 卸载导入到 spring 容器中的 bean, 通过bean名称卸载
        List<String> registeredBeanNamesList = registeredBeanNames.stream().toList();
        // 循环 通过bean名称卸载
        for (int i = 0; i < registeredBeanNamesList.size(); i++) {
            // bean的名称
            String beanName = registeredBeanNamesList.get(i);
            // 如果spring中存在这个bean的名称
            if (defaultListableBeanFactory.containsBeanDefinition(beanName)) {
                // 触发 Bean 的销毁方法
                Object beanInstance = defaultListableBeanFactory.getSingleton(beanName);
                if (beanInstance != null) {
                    // 销毁bean
                    defaultListableBeanFactory.destroySingleton(beanName);
                }
                // 从spring中 移除 BeanDefinition (移除bean的定义)
                defaultListableBeanFactory.removeBeanDefinition(beanName);
                System.out.println("Bean 已卸载: " + beanName);
            }
        }
        // 清理缓存 确保卸载时清理元数据缓存 避免依赖解析失败
        defaultListableBeanFactory.clearMetadataCache(); // 清理元数据缓存
        // ----------------------------------------------------------------------------------------------------------------

        // 清空保存的内容
        classesToRegister.clear();
        registeredBeanNames.clear();
        // ----------------------------------------------------------------------------------------------------------------

        System.out.println("卸载导入的jar里面的class完成");
        return "JAR unloaded successfully.";
    }
    // ----------------------------------------------------------------------------------------------------------------

}
